import { Injectable } from '@angular/core';
import * as $ from 'jquery';

import { EventService, EventMessage, EventSource, ParticipantMoveEvent, ParticipantSelectEvent, ParticipantSizeEvent, ParticipantGoHomeEvent, ParticipantMuteChangeEvent } from '../services/event.service';
import { RequestReplyService } from './request-reply.service';
import { User, AuthUser, OPosition } from '../entity/TT';
import { TrackType, Track, TrackWrapper, CONNECTION_STATUS } from '../entity/ORTC';
import { PainterEvent, ToolType } from '../entity/ToolItem';

const TAG = 'OrtcService';

export class XMPPMessage {
  id: string;
  text: string;
  ts: number;
}

@Injectable({
  providedIn: 'root'
})
export class OrtcService {

  options: any ;


  // JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
  initOptions: any = {
    disableAudioLevels: true,

    // The ID of the jidesha extension for Chrome.
    desktopSharingChromeExtId: 'mbocklcggfhnbahlnepmldehdhpjfcjp',

    // Whether desktop sharing should be disabled on Chrome.
    desktopSharingChromeDisabled: true,

    // The media sources to use when using screen sharing with the Chrome
    // extension.
    desktopSharingChromeSources: ['screen', 'window'],

    // Required version of Chrome extension
    desktopSharingChromeMinExtVersion: '0.1',

    // Whether desktop sharing should be disabled on Firefox.
    desktopSharingFirefoxDisabled: true
  };

  confOptions = {
    openBridgeChannel: true
  };

  localTracks = [];
  remoteTracks: TrackWrapper[] = [];
  isJoined = false;

  connection: any = null;
  room: any = null;
  smartBoard: any = null;

  constructor(private eventService: EventService,
              private requestService: RequestReplyService) { }


  init(user: AuthUser) {
    console.log('init user:', user);
    this.requestService.GetRtc().subscribe((options) => {
        options.bosh=options.bosh+'?room=test'
        // options.bosh=''

        options.hosts.anonymousdomain='meet.jitsi'
        this.options = options;
        console.log("options", this.options)
        this.initJitsi();

        //
        // id: user.uin + '@' + this.options.hosts.domain,
        // password: user.password
        this.initConnection({
          id: user.uin + '@' + this.options.hosts.domain,
        password: user.password });

    });
  }

  initJitsi() {
    console.log("initOptions", this.initOptions)

    JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.INFO);
    JitsiMeetJS.init(this.initOptions);
  }

  initConnection(connOptions: any) {
    console.log('initConnection:', connOptions)

    this.connection = new JitsiMeetJS.JitsiConnection(null, null, this.options);

    this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, this.onConnectionSuccess.bind(this));

    this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, this.onConnectionFailed.bind(this));

    this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, this.disconnect.bind(this));

    console.log('connection using:', connOptions);
    this.connection.connect();

  }

  sendText(text: string): boolean {
    if (!text) {
      return false;
    }

    if (!this.room) {
      console.log('room is null');
      return;
    }

    this.room.sendTextMessage(text);
    console.log('sent');
    return true;
  }

  joinConference(conference: string) {

    if (!conference) {
      console.warn('conference is invalid');
      return;
    }

    conference = conference.toLocaleLowerCase();
    console.log('joinConference:', conference);

    if (!this.connection) {
      console.warn('connection is invalid');
      return;
    }

    JitsiMeetJS.mediaDevices.addEventListener(JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED, this.onDeviceListChanged.bind(this));

    const createPromise = JitsiMeetJS.createLocalTracks({ devices: ['audio', 'video'] })
      .then(this.onLocalTracks.bind(this))
      .catch(error => {
        this.eventService.sendError(error.message);
        console.log('createLocalTracks', error);
        throw error;
      });

    if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
      console.log('mediaDevices.enumerateDevices', JitsiMeetJS.mediaDevices.enumerateDevices)
      JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
        const audioOutputDevices
          = devices.filter(d => d.kind === 'audiooutput');

        if (audioOutputDevices.length > 1) {
          $('#audioOutputSelect').html(
            audioOutputDevices
              .map(
                d =>
                  `<option value="${d.deviceId}">${d.label}</option>`)
              .join('\n'));

          $('#audioOutputSelectWrapper').show();
        }
      });
    }

    try {
      this.room = this.connection.initJitsiConference(conference, this.confOptions);
    } catch (e) {
      console.error('initJitsiConference', e);
      return;
    }

    this.room.on(JitsiMeetJS.events.conference.TRACK_ADDED, this.onRemoteTrack.bind(this));

    this.room.on(JitsiMeetJS.events.conference.TRACK_REMOVED, track => {
      console.log(`track removed!!!${track}`);
    });

    this.room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, this.onConferenceJoined.bind(this));

    this.room.on(JitsiMeetJS.events.conference.USER_JOINED,  this.onUserJoined.bind(this));

    this.room.on(JitsiMeetJS.events.conference.USER_LEFT, this.onUserLeft.bind(this));

    this.room.on(JitsiMeetJS.events.conference.TRACK_MUTE_CHANGED, track => {
      const pId = track.getParticipantId();
      console.log(`${pId} - ${track.getType()} isMuted - ${track.isMuted()}`);

      const pmce: ParticipantMuteChangeEvent = {participantId : pId, active: track.isMuted() };
      this.eventService.sendMuteChanged( pmce );
    });

    this.room.on(JitsiMeetJS.events.conference.MESSAGE_RECEIVED,
      (id: string, text: string, ts: number = new Date().getTime()) => {
        console.log('MESSAGE_RECEIVED:', id, text, ts);
        const xmppMessage: XMPPMessage = { id, text, ts };
        this.eventService.sendXMPPMessage(xmppMessage);
        return true;
      });

    this.room.on(
      JitsiMeetJS.events.conference.DISPLAY_NAME_CHANGED,
      (userID, displayName) => console.log(`${userID} - ${displayName}`));

    this.room.on(
      JitsiMeetJS.events.conference.TRACK_AUDIO_LEVEL_CHANGED,
      (userID, audioLevel) => console.log(`${userID} - ${audioLevel}`));

    this.room.on(
      JitsiMeetJS.events.conference.PHONE_NUMBER_CHANGED,
      () => console.log(`${this.room.getPhoneNumber()} - ${this.room.getPhonePin()}`));

    this.room.join();

    this.onUserJoined(this.myParticipantId(), null);
  }

  getXmpp() {
    return this.connection.xmpp;
  }

  initSmartBoard() {
    const xmpp = this.getXmpp();
    if (!xmpp) {
      console.warn('xmpp', xmpp);
      return false;
    }

    this.smartBoard = xmpp.initSmartBoard(this.onController.bind(this), this.onDraw.bind(this));

    this.eventService.subscribeDraw((draw: PainterEvent) => {
      console.log('subscribeDraw', draw);
      this.smartBoard.sendDraw(this.myParticipantId(), [0], draw);
    });

    this.eventService.subscribeEvent((evt: EventMessage) => {
      switch (evt.source) {
        case EventSource.TOGGLE_TOOLBOX:
          this.sendControllerTogglePainter(evt.data);
          break;

        case EventSource.PARTICIPANT_MOVE:
          this.sendControllerStudentMove(evt.data);
          break;

        case EventSource.PARTICIPANT_SELECT:
          this.sendControllerStudentSelect(evt.data);
          break;

        case EventSource.PARTICIPANT_SIZE:
          const data: ParticipantSizeEvent = evt.data;
          this.smartBoard.sendControl(this.myParticipantId(),
            [data.participantId], {'participant-size': data.size }
          );
          break;
        case EventSource.MUTE_VIDEO:
          console.log('sssssssss')
          this.toggleMute(TrackType.video, !evt.data);
          break;
      }


    });

  }

  onDraw($draw, ts) {
    console.log('onDraw', ts);

    const participants = $draw.find('participants').text().split(',');
    console.log('participants', participants);
    const from = $draw.find('from').text();

    if (this.myParticipantId() == from) {
      console.log('is mine');
      return;
    }


    const $data = $draw.find('draw>data');

    const drawType: ToolType = $data.find('type').text();
    const $drawData = $data.find('data');
    let data;
    switch (drawType) {
      case ToolType.TEXT:
          data = {text: $drawData.find('text').text(),
            font: $drawData.find('font').text(), color: $drawData.find('color').text(),
            position: {x: $drawData.find('position>x').text(), y: $drawData.find('position>y').text()}};
          break;
      // case ToolType.PEN:

          // data = {  width: $drawData.find('width').text(),
          //           color: $drawData.find('color').text(),
          //           points: $drawData.find('points').text()
          //         }

        // break;
      case ToolType.MOVE_SECTION:
        data = {x: $drawData.find('x').text(), y: $drawData.find('y').text(),
                width: $drawData.find('width').text(), height: $drawData.find('height').text()
              };
        break;
      case ToolType.MOVE_MOVE:
        data = {x: $drawData.find('x').text(), y: $drawData.find('y').text()};
        break;
      default:
          data = $drawData.text();
          break;
    }

    const pe: PainterEvent = { type: drawType, data };
    console.log('PainterEvent', pe);

    this.eventService.sendPaintChange(pe);
  }

  onController($controller, ts) {
    console.log('onController', ts);

    const myId = this.myParticipantId();
    console.log('myId', myId);

    const from = $controller.find('from').text();
    console.log('from', from);

    const participants = $controller.find('participants').text().split(',');
    console.log('participants', participants);

    if (!participants || participants.length < 1) {
      return;
    }

    const isMine = participants.find(p => p == myId);
    if (isMine) {
      console.log('has me');
      const voice = $controller.find('data>voice').text();
      if(voice != null && voice != ''){
        const active:boolean = voice == 'true'
        this.toggleMute(TrackType.audio, active )
      }
      
      const whiteboard = $controller.find('data>whiteboard').text();
      if (whiteboard != null && whiteboard != '') {
        this.eventService.sendWhiteBoardChanged(whiteboard == 'true');
      }
    }

    if (from != myId) {
      // not mine
      const $participantMove = $controller.find('data>participant-move');
      if ($participantMove.length > 0) {
        const x = $participantMove.find('x').text();
        const y = $participantMove.find('y').text();

        const p: OPosition = { x: Number.parseInt(x), y: Number.parseInt(y) };

        const data: ParticipantMoveEvent = {
          participantId: participants[0],
          position: p
        };

        this.eventService.sendEvent(EventSource.PARTICIPANT_MOVED, data);
      }


      const select = $controller.find('data>participant-select').text();
      if (select != null && select != '') {

        const data: ParticipantSelectEvent = {
          participantIds: participants,
          select: select == 'true'
        };

        this.eventService.sendEvent(
          EventSource.PARTICIPANT_SELECTED,
          data
        );
      }

      const width = $controller.find('data>participant-size>width').text();
      const height = $controller.find('data>participant-size>height').text();
      if (width && height) {

        const data: ParticipantSizeEvent = {
          participantId: participants[0],
          size: { width, height }
        };

        this.eventService.sendEvent(
          EventSource.PARTICIPANT_SIZE_CHANGED,
          data
        );
      }

    }

    const gift = $controller.find('data>gift').text();
    if (gift != null && gift != '') {
      this.eventService.sendGiftChanged(participants);
    }

    const togglePainter = $controller.find('data>togglePainter').text();
    if (togglePainter != null && togglePainter != '') {
      this.eventService.sendEvent(EventSource.TOGGLE_TOOLBOX_CHANGED, togglePainter == 'true');
    }

    const goHome = $controller.find('data>participant-go-home').text();
    if (goHome == 'true') {

      participants.forEach(p => {
        const data: ParticipantGoHomeEvent = {
          participantId: p,
          active: goHome == 'true'
        };

        this.eventService.sendEvent(EventSource.PARTICIPANT_GO_HOMEED, data);
      });
    }

  }

  toggleMute(type: TrackType, active:boolean ){
    console.log('toggleMute', type, active)
    this.localTracks.forEach(r => {
      if (r.getType() == type) {
        if (active ) {
          r.unmute();
        } else {
          r.mute();
        }
      }
    })

  }

  sendControllerVoice(participants: string[], active: boolean) {
    if (!participants || participants.length < 1) {
      return;
    }
    this.smartBoard.sendControl(this.myParticipantId(), participants, { voice: active + '' });
  }

  sendControllerWhiteBoard(participants: string[], active: boolean) {
    if (!participants || participants.length < 1) {
      return;
    }
    this.smartBoard.sendControl(this.myParticipantId(), participants, { whiteboard: active + '' });
  }
  
  sendControllerGiftFabulous(participants: string[], active: number) {
    if (!participants || participants.length < 1) {
      return;
    }
    this.smartBoard.sendControl(this.myParticipantId(), participants, { gift: active });
  }

  sendControllerGiftCoin(participants: string[], active: number) {
    if (!participants || participants.length < 1) {
      return;
    }
    this.smartBoard.sendControl(this.myParticipantId(), participants, { gift: active });
  }

  sendControllerTogglePainter(toggle: boolean) {
    this.smartBoard.sendControl(this.myParticipantId(), [0], { togglePainter: toggle + '' });
  }

  sendControllerStudentMove(data: ParticipantMoveEvent) {
    this.smartBoard.sendControl(this.myParticipantId(), [data.participantId], { 'participant-move': data.position });
  }

  sendControllerStudentSelect(data: ParticipantSelectEvent) {
    this.smartBoard.sendControl(this.myParticipantId(), data.participantIds, { 'participant-select': data.select + '' });
  }

  sendControllerStudentGoHome(participants: string[], active: boolean) {
    this.smartBoard.sendControl(this.myParticipantId(), participants, { 'participant-go-home': active + '' });
  }

  myParticipantId() {
    return this.room.myUserId();
  }

  onConnectionSuccess() {
    console.log('onConnectionSuccess', this, this.connection);
    this.eventService.sendXMPPConnectionStatus(CONNECTION_STATUS.ESTABLISHED);
  }

  onConnectionFailed() {
    console.error('Connection Failed!');
  }

  onDeviceListChanged(devices: any) {
    console.info('current devices', devices);
  }

  onLocalTracks(tracks: any[]) {

    console.log('onLocalTracks', tracks);

    this.localTracks = tracks;

    const localTracks = this.localTracks;

    for (let i = 0; i < localTracks.length; i++) {
      const track = localTracks[i];

      console.log('track', track.getType(), track);

      track.addEventListener(
        JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,
        audioLevel => console.log(`Audio Level local: ${audioLevel}`));

      track.addEventListener(
        JitsiMeetJS.events.track.TRACK_MUTE_CHANGED,
        () => console.log('local track muted'));

      track.addEventListener(
        JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED,
        () => console.log('local track stoped'));

      track.addEventListener(
        JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,
        deviceId =>
          console.log(
            `track audio output device was changed to ${deviceId}`));



      if (this.isJoined) {
        this.room.addTrack(track);
      }

    }
  }

  onRemoteTrack(track: any) {

    console.log('onRemoteTrack', track);

    const participant = track.getParticipantId();
    console.log('onRemoteTrack:', participant,
        'type: ', track.getType(),
        'stream.id: ', track.stream.id,
        'isLocal:', track.isLocal());

    if (track.isLocal()) {
      const c: TrackWrapper = this.loadRemoteTrack(participant);
      c.trackMap[track.getType()] = track;
      return;
    }

    track.addEventListener(
      JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,
      audioLevel => console.log(`Audio Level remote: ${audioLevel}`));

    track.addEventListener(
      JitsiMeetJS.events.track.TRACK_MUTE_CHANGED,
      () => console.log('remote track muted'));

    track.addEventListener(
      JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED,
      () => console.log('remote track stoped'));

    track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,
      deviceId => console.log(`track audio output device was changed to ${deviceId}`));


    const c: TrackWrapper = this.loadRemoteTrack(participant);
    c.trackMap[track.getType()] = track;

  }

  loadRemoteTrack(participant: string) {
    let t: TrackWrapper =  this.getRemoteTrack(participant);
    if (!t) {
      t = { participant, index: this.remoteTracks.length, trackMap: {} };
      this.remoteTracks.push(t);
    }
    return t;
  }

  getRemoteTrack(participant: string) {
    const t: TrackWrapper =  this.remoteTracks.find((rt) => rt.participant == participant);
    return t;
  }

  getRemoteTracks() {
    return this.remoteTracks;
  }

  onConferenceJoined() {
    console.log('conference joined!');

    this.eventService.sendXMPPConnectionStatus(CONNECTION_STATUS.CONFERENCE_JOINED);

    this.isJoined = true;

    for (let i = 0; i < this.localTracks.length; i++) {
      this.room.addTrack(this.localTracks[i]);
    }

    this.initSmartBoard();
  }

  onUserLeft(id: any, user: any) {

    console.log(TAG, 'onUserLeft', id, user);

    const track: TrackWrapper = this.getRemoteTrack(id);
    if (!track) {
      return;
    }

    this.eventService.sendUserLeft(track);

  }

  onUserJoined(id: string, user: any)  {
      console.log(TAG, 'user joined', id, user);

      const c: TrackWrapper = this.loadRemoteTrack(id);
      console.log(TAG, 'user TrackWrapper', c);

      this.eventService.sendEvent(EventSource.USER_JOINED, c);
  }

  disconnect() {
    console.log('disconnect!');
    this.connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, this.onConnectionSuccess);
    this.connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, this.onConnectionFailed);
    this.connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, this.disconnect);
  }
}
